"""Modbus TCP 通信工具。

本模块提供将处理后的数据按照需求文档打包成浮点列表并通过 Modbus TCP 协议发送到 PLC 的函数。
网络发送是可选的，调试时可以仅打印输出。"""

from __future__ import annotations

import struct
import socket
from typing import List, Dict, Any, Optional


VERSION_MAGIC = 100.1


def pack_payload(data: List, dis: float, mode: str = "segments") -> List[float]:
    """将处理后的数据和距离打包成一维浮点列表。

    :param data: 处理后的记录列表。在 ``segments`` 模式下，每个元素为
        ``[flag, z_start, z_end]``；在 ``points`` 模式下，每个元素为 ``[flag, z]``。
    :param dis: 距离测量值（单位毫米），未知时可传入 ``-1``。
    :param mode: ``'segments'`` 或 ``'points'``，决定如何解释数据。

    :return: 一个浮点列表，可直接转换为 Modbus 寄存器。序列以 4 个浮点组成的头部
        ``[version, N, status, dis]`` 开始，随后是展开的数据。"""
    floats: List[float] = []
    # 头部
    n = float(len(data)) if mode == "segments" else float(len(data))
    status = 1.0  # 这里使用 1 表示 READY，可用 0 表示 BUSY
    floats.extend([VERSION_MAGIC, n, status, float(dis)])
    # 数据区
    if mode == "segments":
        for rec in data:
            flag, z_start, z_end = rec
            floats.append(float(flag))
            floats.append(float(z_start))
            floats.append(float(z_end))
    elif mode == "points":
        for rec in data:
            flag, z = rec
            floats.append(float(flag))
            floats.append(float(z))
    else:
        raise ValueError(f"不支持的输出模式: {mode}")
    return floats


def floats_to_registers(values: List[float]) -> List[int]:
    """将浮点数列表转换为 16 位寄存器列表。

    每个浮点数按 IEEE‑754 单精度大端编码，占用两个 16 位寄存器。
    返回列表的长度为 ``2 * len(values)``。"""
    regs: List[int] = []
    for v in values:
        b = struct.pack(">f", float(v))
        hi = int.from_bytes(b[0:2], byteorder="big")
        lo = int.from_bytes(b[2:4], byteorder="big")
        regs.append(hi)
        regs.append(lo)
    return regs


def send_payload(
    floats: List[float],
    host: str = "127.0.0.1",
    port: int = 502,
    unit_id: int = 1,
    reg_base: int = 0,
    page_size: int = 40,
    send: bool = False,
    timeout: float = 2.0,
) -> None:
    """将给定的浮点列表发送至 Modbus TCP 设备，或仅用于调试打印。

    :param floats: 要发送的浮点序列，包括头部和数据区。
    :param host: Modbus 服务器 IP 地址。
    :param port: Modbus 服务器端口（默认 502）。
    :param unit_id: Modbus 单元标识符。
    :param reg_base: 起始寄存器地址。
    :param page_size: 每页最多寄存器数量（此实现未使用）。
    :param send: 为 True 时实际通过 TCP 发送；为 False 时仅打印寄存器内容。
    :param timeout: 套接字超时时间（秒）。"""
    regs = floats_to_registers(floats)
    # 调试时打印待发送数据
    print("发送 Modbus 载荷（浮点）:", floats)
    print("转换后的寄存器（大端 16 位）:", regs)
    if not send:
        return
    # 构建用于写多个寄存器的 Modbus TCP 帧（功能码 16）
    # 本实现一次发送全部寄存器；对于消息过长的情况可以根据 page_size 分页。
    fc = 16  # 功能码 16
    start_addr = reg_base
    quantity = len(regs)
    # 计算字节数（每个寄存器 2 个字节）
    byte_count = quantity * 2
    # 构建 PDU：功能码 + 起始地址 + 寄存器数量 + 字节计数 + 寄存器字节
    pdu = struct.pack(
        ">BHHB",
        fc,
        start_addr,
        quantity,
        byte_count,
    )
    # 附加寄存器字节
    reg_bytes = b"".join(struct.pack(">H", r) for r in regs)
    pdu += reg_bytes
    # MBAP 头部：事务 ID（2 字节）、协议 ID（2 字节）、长度（2 字节）、单元 ID（1 字节）
    transaction_id = 1  # 事务 ID 固定值，可在多次请求中递增
    protocol_id = 0
    length = len(pdu) + 1  # 长度字段，单位 ID + PDU 的长度
    mbap = struct.pack(
        ">HHHB",
        transaction_id,
        protocol_id,
        length,
        unit_id,
    )
    frame = mbap + pdu
    # 通过 TCP 发送帧
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.settimeout(timeout)
        try:
            sock.connect((host, port))
        except Exception as e:
            print(f"连接 Modbus 服务器 {host}:{port} 失败: {e}")
            return
        try:
            sock.sendall(frame)
            # 等待响应
            resp = sock.recv(1024)
            print("收到响应:", resp)
        except Exception as e:
            print(f"发送 Modbus 帧时出错: {e}")